#include "../common/config.h"
LoggerPtr gLogger = Logger::getLogger("fsbill");
int  gRunThread = 1;

apr_queue_t* jobqueue = NULL;


struct bill_thread_info
{
    char* server;
    int   port;
    char* pFsAuth;
    bill_thread_info()
            : server(NULL)
            , pFsAuth(NULL)
    {

    }
    ~bill_thread_info()
    {

        if (server != NULL)
        {
            free(server);
            server = NULL;
        }

        if (pFsAuth != NULL)
        {
            free(pFsAuth);
            pFsAuth = NULL;
        }
    }
};

void event_hangup(esl_event_t* ievent, const char* pHost)
{
    const char* uuid = esl_event_get_header(ievent, "unique-id");
    const char* account = esl_event_get_header(ievent, "variable_orig_acct");
    const char* caller = esl_event_get_header(ievent, "variable_orig_caller");
    const char* called = esl_event_get_header(ievent, "variable_orig_called");
    const char* pcalltype = esl_event_get_header(ievent, "variable_calltype");

    //const char* dialstatus = esl_event_get_header(ievent, "answer-state");
    const char* billsec = esl_event_get_header(ievent, "variable_billsec");
    const char* startTime = esl_event_get_header(ievent, "variable_start_stamp");
    const char* endTime = esl_event_get_header(ievent, "variable_end_stamp");
    const char* pTohost = esl_event_get_header(ievent, "variable_sip_to_host");

    if (called==NULL)
    {
         called="";
    }
    if (!(uuid && account && caller && called && pcalltype && billsec && startTime && endTime && atoi(billsec) > 0))
    {
        LOG4CXX_DEBUG(gLogger, "uuid=" << (uuid ? uuid : "none") << "\tacct=" << (account ? account : "none") << "\tcaller=" << (caller ? caller : "none") << "\tcalled=" << (called ? called : "none") << "\tcalltype=" << (pcalltype ? pcalltype : "none")
                      << "\tstarttime=" << (startTime ? startTime  : "none") << "\tendTime=" << (endTime ? endTime : "none") << "\tanswertime=" << billsec << "\thost=" << pTohost << " event invaild");
        return;
    }


    CALL_TYPE callType = (CALL_TYPE) atoi(pcalltype);
    int nAnserTime = atoi(billsec);
    bill(callType, account, caller, called, pTohost, startTime, endTime, nAnserTime);
    LOG4CXX_DEBUG(gLogger, account << '\n' << caller << '\n' << called << '\n' << nAnserTime << "\n" << startTime << '\n' << endTime << '\n' << pTohost );
}


static void *msg_thread_run(esl_thread_t *me, void *obj)
{

    bill_thread_info*   pCtrl = (bill_thread_info*) obj;
    char* host = pCtrl->server;

    esl_handle_t handle = {{0}};

    if (esl_connect(&handle, pCtrl->server, pCtrl->port, NULL, pCtrl->pFsAuth) == ESL_SUCCESS)
    {
        LOG4CXX_INFO(gLogger, "connect " << pCtrl->server << ":" << pCtrl->port << " success");
    }
    else
    {
        LOG4CXX_WARN(gLogger, "connect " << pCtrl->server << ":" << pCtrl->port << " failed wait for 1 second");
        apr_sleep(APR_USEC_PER_SEC);
        goto done;
    }

    if (esl_events(&handle, ESL_EVENT_TYPE_PLAIN, "CHANNEL_HANGUP_COMPLETE") == ESL_FAIL)
    {
        LOG4CXX_WARN(gLogger, host << " set event plain faild,contact admin");
        goto done;
    }

    while (gRunThread && handle.connected)
    {
        esl_status_t status = esl_recv_event_timed(&handle, 10, 1, NULL);

        if (status == ESL_FAIL)
        {
            LOG4CXX_WARN(gLogger, host << " Disconnected.wait for reconnect! ");
            goto done;
        }
        else if (status == ESL_SUCCESS)
        {
            if (handle.last_event)
            {
                const char *type = esl_event_get_header(handle.last_event, "content-type");

                if (!esl_strlen_zero(type))
                {
                    if (!strcasecmp(type, "text/event-plain"))
                    {
                        const char* eventname = esl_event_get_header(handle.last_ievent, "event-name");

                        if (!esl_strlen_zero(eventname))
                        {
                            if (!strcasecmp(eventname, "CHANNEL_HANGUP_COMPLETE"))
                            {
                                event_hangup(handle.last_ievent, host);
                            }
                        }
                    }
                }
            }
        }

        apr_sleep(1000);
    }

done:

    if (gRunThread)
    {
        apr_queue_push(jobqueue, pCtrl);
    }

    return NULL;
}
static void sighandler( int sig_no )
{
    gRunThread = 0;
}
#ifndef _DEBUG
int localMain(int argc, char* argv[]);
int main(int argc, char *argv[])
{
    if (init_daemon(argc, argv, localMain) < 0)
    {
        return 0;
    }

    return 0;
}
int localMain(int argc, char* argv[])
#else
int main(int argc, char *argv[])
#endif
{
    signal(SIGUSR1, sighandler);
    apr_initialize();
    CIniFile config;
    string strCfgPath("/etc/fsbill.ini");

    if (argc >= 2)
    {
        strCfgPath = argv[1];
    }

    ostringstream ss;

    config.SetPath(strCfgPath);
    config.ReadFile();
    PropertyConfigurator::configure(strCfgPath);


    const string password = config.GetValue("bill", "fsstr", "66call");
    const string strClient = config.GetValue("bill", "callserver", "");
    const int    nThreadPool = config.GetValueI("bill", "threads", 10);

    LOG4CXX_TRACE(gLogger, "config=" << strCfgPath << "\tfsstr=" << password << "\tclient=" << strClient << "\tThreadpool=" << nThreadPool);


    //initalize线程池
    apr_pool_t *main_Pool;
    apr_thread_pool_t *thrp;

    if (apr_pool_create(&main_Pool, NULL) != APR_SUCCESS)
    {
        LOG4CXX_WARN(gLogger, "apr_pool_create error");
        apr_terminate();
        return -1;
    }

    LOG4CXX_WARN(gLogger, "init memory pool ok");

    if (apr_queue_create(&jobqueue, nThreadPool, main_Pool) != APR_SUCCESS)
    {
        LOG4CXX_WARN(gLogger, "apr_pool_create error");
        apr_pool_destroy(main_Pool);
        apr_terminate();
        return -1;
    }

    LOG4CXX_WARN(gLogger, "init call job queue pool ok");

    if (apr_thread_pool_create(&thrp, nThreadPool, nThreadPool, main_Pool) != APR_SUCCESS)
    {
        LOG4CXX_WARN(gLogger, "apr_thread_pool_create error,thread pool=" << nThreadPool);
        apr_pool_destroy(main_Pool);
        apr_terminate();
        return -2;//线程池创建失败
    }

    LOG4CXX_WARN(gLogger, "init Thread=" << nThreadPool << " pool ok");

    if (!initDBS(config, "bill-db", main_Pool))
    {
        LOG4CXX_WARN(gLogger, "initDBS error");
        goto clean;
    }

    LOG4CXX_WARN(gLogger, "init database pool ok");

    //创建客户监视线程
    if (strClient.length() > 0)
    {
        string delimiter(",");
        const vector<string>& clientlist = split(strClient, delimiter);

        for (vector<string>::const_iterator iter = clientlist.begin(); iter != clientlist.end(); ++iter)
        {
            char* pIp = strdup((*iter).c_str());
            char* pPort = strchr(pIp, ':');

            if (pPort != NULL)
            {
                *pPort++ = '\0';
            }
            else
            {
                pPort = (char*)"8021";
            }

            bill_thread_info* pCtrl = new bill_thread_info();
            pCtrl->server = strdup(pIp);
            pCtrl->port = atoi(pPort);
            pCtrl->pFsAuth = strdup(password.c_str());
            apr_queue_push(jobqueue, pCtrl);
            free(pIp);
        }
    }

    esl_global_set_logger(NULL);




    while (gRunThread)
    {
        bill_thread_info* pCtrl = NULL;
        apr_status_t ret = apr_queue_pop (jobqueue, (void**) & pCtrl);

        if (ret == APR_SUCCESS)
        {
            LOG4CXX_INFO(gLogger, "create monitor thread for <" << pCtrl->server << ":" << pCtrl->port << ">");

            if (esl_thread_create_detached(msg_thread_run, pCtrl) != ESL_SUCCESS)
            {
                apr_queue_push(jobqueue, pCtrl);
            }
        }

        else if (APR_EINTR)
        {
            apr_sleep(APR_USEC_PER_SEC);
        }
        else if (ret == APR_EOF)
        {
            gRunThread = 0;
            break;
        }
    }


clean:
    apr_queue_term(jobqueue);
    apr_thread_pool_destroy(thrp);
    apr_pool_destroy(main_Pool);
    apr_terminate();
    return 0;
}

